package com.db.logger;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class AsyncFileAppender implements Appender {
    private final BlockingQueue<LogEvent> buffer;
    private final Thread dispatcher;
    private final int DEFAULT_BUFFER_SIZE = 128;
    private final String logFile = "log.txt";
    private PrintWriter writer;
    @Override
    public void append(LogEvent logEvent) {
        try {
            buffer.put(logEvent);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private static class SingletonHolder {
        public static final AsyncFileAppender HOLDER_INSTANCE = new AsyncFileAppender();
    }

    public static AsyncFileAppender getInstance() {
        return SingletonHolder.HOLDER_INSTANCE;
    }

    public AsyncFileAppender() {
        buffer = new ArrayBlockingQueue<>(DEFAULT_BUFFER_SIZE);
        try {
            writer = new PrintWriter(new BufferedWriter(new FileWriter(logFile, true)), true);
        } catch (IOException e) {
            writer = new PrintWriter(System.out, true);
        }

        dispatcher = new Thread(new Dispatcher());
        dispatcher.setDaemon(true);
        dispatcher.setPriority(Thread.MIN_PRIORITY);
        dispatcher.start();
    }

    class Dispatcher implements Runnable {

        @Override
        public void run() {
            final SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss:SSS");
            while (true) {
                try {
                    LogEvent e = buffer.take();
                    String msg = sdf.format(e.getDate()) + " " + e.getLevel() + " ["
                            + e.getThread() + "] " + e.getLogger() + " : "
                            + e.getMessage();
                    writer.println(msg);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } catch (Exception e) {
                    System.err.format("[ERROR] %s : Dispatcher thread caught %s (%s) %n",
                            this.getClass(), e, e.getMessage() != null ? e.getMessage() : "");
                }
            }
        }
    }
}
